#ifndef VECTOR3_H
#define VECTOR3_H
#include <string.h>
#include <stdlib.h>
#include <string>
#include "DeviceObject.cu"
#include "global_object.h"
using namespace std;

class Vector3 : public DeviceObject
{
public:
	
    /**
     * Default constructor. Initializes all values to 0
     */
    inline __host__ __device__  Vector3(){
        memset(data, 0, 3*sizeof(float));
		size = sizeof(Vector3);
    }

    inline __host__ __device__  Vector3(const float xval, const float yval, const float zval){
        data[0] = xval;
        data[1] = yval;
        data[2] = zval;
		size = sizeof(Vector3);
    }

    inline __host__ __device__  const float& operator[](const int i) const
    { 
      return data[i];
    }
    
    inline __host__ __device__  float& operator[](const int i)
    { 
      return data[i];
    }

    /**
     * Destructively normalizes the Vector3
     * @return a reference to the Vector3 after normalization
     */
    inline __host__ __device__  Vector3& normalize();

    /**
     * Calculates the dot product between two vectors
     * @return
     */
    inline __host__ __device__  float dot(const Vector3& v) const;

    /**
     * Calculates the Cross product between two vectors
     * @param v the Vector3 to be crossed with
     * @return the Vector3
     */
    inline __host__ __device__  Vector3 cross(const Vector3& v) const ;

    /**
     * Computes the magnitude of the Vector3
     * @return the magnitude of the Vector3
     */
    inline __host__ __device__  float magnitude() const;

    /**
     * Multiplies each channel of this vector
     * with the same channel of the given vector
     * @param v Vector3 to mix with
     * @return the mixed vector
     */
    inline __host__ __device__  Vector3 mix(const Vector3& v) const;

    /**
     * Computes a weighted average Vector3 by mixing this vector
     * scaled by the inverse of weight with the given vector scaled by weight
     * @param v the vector to mix this vector with
     * @param weight the scale factor for the given vector
     * @return a weighted average vector
     */
    inline __host__ __device__  Vector3 weightedAverage(const Vector3& v, float weight) const ;

    /**
     * Destructively caps the data values of this vector to be less than
     * or equal to the given value
     * @param value the maximum value for this vector
     * @return this vector
     */
    inline __host__ __device__  Vector3& cap(float value);

    inline __host__ __device__  Vector3 bisect(const Vector3& v) const;
    
    /**
     * Assignment operator
     * @param rhs Vector3 with the new values
     * @return this
     */
    inline __host__ __device__  const Vector3 & operator=(const Vector3 &rhs) {
        data[0] = rhs.data[0];
        data[1] = rhs.data[1];
        data[2] = rhs.data[2];
        return *this;
    }

    /**
     * Addition and assignment operator
     * @param rhs Vector to be summed into this vector
     * @return this
     */
    inline __host__ __device__  Vector3 & operator+=(const Vector3 &rhs) {
        data[0] += rhs.data[0];
        data[1] += rhs.data[1];
        data[2] += rhs.data[2];
        return *this;
    }

    /**
     * Subtraction and assignment operator
     * @param rhs Vector3 to be subtracted from this vector
     * @return this
     */
   inline __host__ __device__  Vector3 & operator-=(const Vector3 &rhs) {
        data[0] -= rhs.data[0];
        data[1] -= rhs.data[1];
        data[2] -= rhs.data[2];
        return *this;
    }

    /**
     * Constant multiplication operator
     * @param c constant float to multiply this vector by
     * @return this
     */
    inline __host__ __device__  Vector3 & operator*=(const float c) {
        data[0] = data[0]*c;
        data[1] = data[1]*c;
        data[2] = data[2]*c;
        return *this;
    }

    /**
     * Constant division operator
     * @param c constant float to divide this vector by
     * @return this
     */
    inline __host__ __device__  Vector3 & operator/=(const float c) {
        data[0] = data[0]/c;
		data[1] = data[1]/c;
		data[2] = data[2]/c;
		return *this;
    }

	/**
	* Prints the vector's values
	*/
	inline __host__ __device__  void print() {
		printf("| %.3f | %.3f | %.3f |", data[0], data[1], data[2]);
	}

	inline __host__ __device__ float3 toFloat3(){
		return make_float3(data[0], data[1], data[2]);
	}

	inline __host__ __device__ const Vector3 abs() const {
		float x = (data[0] < 0)? -data[0] : data[0];
		float y = (data[1] < 0)? -data[1] : data[1];
		float z = (data[2] < 0)? -data[2] : data[2];
		return Vector3(x, y, z);
	}

protected:
    float data[3];

private:

};

inline __host__ __device__  const Vector3 operator+(const Vector3& lhs, const Vector3& rhs);
inline __host__ __device__  const Vector3 operator-(const Vector3& lhs, const Vector3& rhs);
inline __host__ __device__  const Vector3 operator+(const Vector3& lhs, const float rhs);
inline __host__ __device__  const Vector3 operator-(const Vector3& lhs, const float rhs);
inline __host__ __device__  const Vector3 operator*(const Vector3& lhs, const float rhs);
inline __host__ __device__  const Vector3 operator*(const float rhs, const Vector3 &lhs);
inline __host__ __device__  const Vector3 operator/(const Vector3& lhs, const float rhs);
inline __host__ __device__  bool operator<(const Vector3& lhs, const Vector3& rhs);
inline __host__ __device__  bool operator<=(const Vector3& lhs, const Vector3& rhs);
inline __host__ __device__  bool operator>(const Vector3& lhs, const Vector3& rhs);
inline __host__ __device__  bool operator>=(const Vector3& lhs, const Vector3& rhs);

#endif